// Copyright (C) 2013 - Will Glozer.  All rights reserved.
#include "script.h"
#include <string.h>
#include <iostream>
#include <sstream>
#include "wrk_record.h"
#include <netdb.h>

#include <stdlib.h>
#include <stddef.h>
#include "http_parser.h"
#include "zmalloc.h"
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include "units.h"
#include "wrk_worker.h"
#include <stdlib.h>


typedef struct {
    char *name;
    int   type;
    void *value;
} table_field;

static int script_addr_tostring(lua_State *);
static int script_addr_gc(lua_State *);
static int script_stats_call(lua_State *);
static int script_stats_len(lua_State *);
static int script_stats_index(lua_State *);
static int script_thread_index(lua_State *);
static int script_thread_newindex(lua_State *);
static int script_wrk_lookup(lua_State *);
static int script_wrk_connect(lua_State *);
static int script_hmac_md5(lua_State *L);

static int script_connection_t_tostring(lua_State *L);
static int script_connection_t_index(lua_State *L);
static wrk_core_connection_t *check_connection_t(lua_State *L);
static void script_push_connection_t(lua_State *L, wrk_core_connection_t *c);

static void set_fields(lua_State *, int, const table_field *);
static void set_field(lua_State *, int, char *, int);
static int push_url_part(lua_State *, char *, struct http_parser_url *, enum http_parser_url_fields);

static const struct luaL_Reg addrlib[] = {
    { "__tostring", script_addr_tostring   },
    { "__gc"    ,   script_addr_gc         },
    { NULL,         NULL                   }
};

static const struct luaL_Reg statslib[] = {
    { "__call",     script_stats_call      },
    { "__index",    script_stats_index     },
    { "__len",      script_stats_len       },
    { NULL,         NULL                   }
};

static const struct luaL_Reg threadlib[] = {
    { "__index",    script_thread_index    },
    { "__newindex", script_thread_newindex },
    { NULL,         NULL                   }
};


static const struct luaL_Reg connectionlib[] = {
    { "__tostring", script_connection_t_tostring},
    { "__index",    script_connection_t_index   },
    { NULL,         NULL                   }
};

char *script_get_wrk_field(lua_State *L, const char *name)
{
    lua_getglobal(L, "wrk");
    lua_getfield(L, -1, name);
    char *v = (char *)lua_tostring(L, -1);
    return v;
}

wrk_core_model_t* script_get_model(lua_State *L)
{
    int top = lua_gettop(L);
    // auto m = std::make_shared<wrk_core_model_t>();
    wrk_core_model_t *m = new wrk_core_model_t();
    lua_getglobal(L, "wrk");
    lua_pushstring(L, "model");
    lua_gettable(L, -2);

    if(lua_type(L, -1) !=LUA_TTABLE) throw string("wrk.model is't table!");

    lua_pushstring(L, "timeout");

    lua_gettable(L, -2);

    m->timeout = 1000 * lua_tointeger(L, -1);

    lua_pop(L, 1);

    lua_pushstring(L, "url");
    lua_gettable(L, -2);
    size_t length = 0;
    const char *str = lua_tolstring(L, -1, &length);
    m->url = (char *)realloc((void *)m->url, length + 1);
    memset((void *)m->url, length + 1, 0);
    memcpy((void *)m->url, str, length +1);

    lua_pop(L, 1);

    lua_pushstring(L, "phase");
    lua_gettable(L, -2);
    if(lua_type(L, -1) !=LUA_TTABLE) throw string("wrk.model.phase is't table!");

    size_t len = lua_objlen(L, -1);
    m->phase_cnt = len;
    wrk_core_phase_t *phase = new wrk_core_phase_t[len]();
    for (int i = 1; i <= len; i++) {
        lua_pushinteger(L, i);
        lua_gettable(L, -2);
        /* 此时栈顶即为 t[i] 元素 */
        if(lua_type(L, -1) !=LUA_TTABLE) {
            std::stringstream ss;
            ss << "wrk.model.phase["<< i <<"] is't table";
            throw string(ss.str());
        }

        lua_pushstring(L, "connections");
        lua_gettable(L, -2);
        uint64_t connections = 0;
        scan_metric((char *)lua_tostring(L, -1) , &connections);
        if(connections<=0) throw string("connetions must be grater than 0!"); 
        phase[i-1].connections = connections;
        phase[i-1].id = i;
        lua_pop(L, 1);

        lua_pushstring(L, "duration");
        lua_gettable(L, -2);
        uint64_t duration = 0;
        scan_time((char *)lua_tostring(L, -1) , &duration);
        if(duration<=0) throw string("duration must be grater than 0!"); 
        phase[i-1].duration = duration;
        lua_pop(L, 1);

        lua_pop(L, 1);
    } // end for

    lua_pop(L, lua_gettop(L) - top);
    m->phase = phase;

    return m;
}

lua_State *script_create(char *file) {
    stringstream ss;
    lua_State *L = luaL_newstate();
    lua_atpanic(L, newPanic);
    luaL_openlibs(L);
    (void) luaL_dostring(L, "wrk = require \"wrk\"");

    luaL_newmetatable(L, "wrk.addr");
    luaL_register(L, NULL, addrlib);

    luaL_newmetatable(L, "wrk.stats");
    luaL_register(L, NULL, statslib);

    luaL_newmetatable(L, "wrk.thread");
    luaL_register(L, NULL, threadlib);

    luaL_newmetatable(L, "wrk.connection");
    luaL_register(L, NULL, connectionlib);

    // LUA脚本存在会加载这个文件，前面已经执行了wrk.lua，最终还是会返回lua_State
    if (file && luaL_dostring(L, file)) {
        const char *cause = lua_tostring(L, -1);
        // lua_close(L);
        ss << cause;
        throw ss.str();
    }


    wrk_core_model_t *m_ptr = script_get_model(L);
    char *s_url = (char *)m_ptr->url;
    struct http_parser_url parts = {};
    if (!script_parse_url(s_url, &parts)) {
        lua_close(L);
        ss << "invalid URL: "<< s_url;
        throw ss.str();
    }

    char *path = "/";

    if (parts.field_set & (1 << UF_PATH)) {
        path = &s_url[parts.field_data[UF_PATH].off];
    }

    const table_field fields[] = {
        { "lookup",  LUA_TFUNCTION, (void *)script_wrk_lookup  },
        { "connect", LUA_TFUNCTION, (void *)script_wrk_connect },
        { "hmac_md5", LUA_TFUNCTION, (void *)script_hmac_md5 },
        { "path",    LUA_TSTRING,   (void *)path               },
        { NULL,      0,             NULL               },
    };

    lua_getglobal(L, "wrk");
    set_field(L, 5, "scheme", push_url_part(L, s_url, &parts, UF_SCHEMA));
    set_field(L, 5, "host",   push_url_part(L, s_url, &parts, UF_HOST));
    set_field(L, 5, "port",   push_url_part(L, s_url, &parts, UF_PORT));
    set_fields(L, 5, fields);

    lua_pop(L, lua_gettop(L));

    return L;
}


static int script_hmac_md5(lua_State *L)
{
    u_char                  *sec, *sts;
    size_t                   lsec, lsts;
    unsigned int             md_len;
    unsigned char            md[EVP_MAX_MD_SIZE];
    char                     ot[EVP_MAX_MD_SIZE];
    const EVP_MD            *evp_md;

    if (lua_gettop(L) != 2) {
        return luaL_error(L, "expecting 2 arguments, but got %d",
                          lua_gettop(L));
    }

    sec = (u_char *) luaL_checklstring(L, 1, &lsec);
    sts = (u_char *) luaL_checklstring(L, 2, &lsts);

    evp_md = EVP_md5();

    HMAC(evp_md, sec, lsec, sts, lsts, md, &md_len);
    for (int i = 0; i < md_len; i++)  
        sprintf(ot + i * 2, "%02x", md[i]);

    lua_pushlstring(L, (char *) ot, md_len*2);

    return 1;
}

bool script_resolve(lua_State *L, char *host, char *service) {
    lua_getglobal(L, "wrk");

    lua_getfield(L, -1, "resolve");
    lua_pushstring(L, host);
    lua_pushstring(L, service);
    if(lua_pcall(L, 2, 0, 0)) {
        throw string("resovle error: ") + string(lua_tostring(L, -1));
    }

    lua_getfield(L, -1, "addrs");
    size_t count = lua_objlen(L, -1);
    lua_pop(L, 2);
    return count > 0;
}

void script_push_thread(lua_State *L, wrk_core_thread_t *t) {
    wrk_core_thread_t **ptr = (wrk_core_thread_t **) lua_newuserdata(L, sizeof(wrk_core_thread_t **));
    *ptr = t;
    luaL_getmetatable(L, "wrk.thread");
    lua_setmetatable(L, -2);
}

void script_init(lua_State *L, wrk_core_thread_t *t) {

    // 把thread压到线程自己的lua栈上, wrk.thread即可操作
    lua_getglobal(t->L, "wrk");
    script_push_thread(t->L, t);
    lua_setfield(t->L, -2, "thread");

    // 这个L是全局的，也就是说setup会影响全局变量
    lua_getglobal(L, "wrk");
    lua_getfield(L, -1, "setup");
    script_push_thread(L, t);
    if(lua_pcall(L, 1, 0, 0)) {
        throw string("init error: ") + string(lua_tostring(L, -1));
    }

    lua_pop(L, 1);

    lua_getfield(t->L, -1, "init");
    if(lua_pcall(t->L, 0, 0, 0)) {
        CDEBUG << "init error: "<< lua_tostring(t->L, -1) <<endl;
        throw string("init error: ") + string(lua_tostring(t->L, -1));
    }
    
    lua_pop(t->L, 1);
}


void script_request(lua_State *L, wrk_core_connection_t *c) {
    if(c->request_length>0) {
        return;
    }

    lua_getglobal(L, "request");
    int pop = 1;
    if (!lua_isfunction(L, -1)) {
        // wrk是个table, wrk.request，非函数情况下，在wrk.lua中通过wrk.init构造wrk.request
        lua_getglobal(L, "wrk");
        lua_getfield(L, -1, "request");
        pop += 2;
    }

    // script_push_connection_t(L, c);
    if(lua_pcall(L, 0, 1, 0)!=0) {
        throw string("call lua request error: ") + string(lua_tostring(L, -1));
    }

    const char *str = lua_tolstring(L, -1, &c->request_length);
    if(str==NULL || c->request_length==0 || c->request_length>1024*1024) {
        printf("call lua request error: request is invaild, request_length = %d", c->request_length);
        return;
    }

    char **buf = &c->request;
    *buf = (char *)zrealloc(*buf, c->request_length);
    memcpy(*buf, str, c->request_length);
    lua_pop(L, pop);
}

int newPanic(lua_State *L) {
    LOG_ERROR("NewPANIC: unprotected error in call to Lua API: %s", lua_tostring(L, -1));
    throw string("PANIC: unprotected error in call to Lua API: ") + string(lua_tostring(L, -1));
    return 0;
}


void script_response(lua_State *L, int status, wrk_core_connection_t *c) {
    lua_getglobal(L, "response");
    lua_pushinteger(L, status);

    script_push_connection_t(L, c);


    if(lua_pcall(L, 2, 2, 0)) {
        throw string("response error: ") + string(lua_tostring(L, -1));
    }

    // lua response 会有与lua request类似返回值request请求体，构造此连接下一个请求
    size_t len = 0;
    const char *str = lua_tolstring(L, -2, &len);
    uint64_t delay = lua_tonumber(L, -1);
    lua_pop(L, 2);
    if(delay>0) {
        c->delayed = true;
        aeEventLoop *loop = c->t->loop;
        aeDeleteFileEvent(loop, c->fd, AE_WRITABLE);
        // LOG_DEBUG("aeDeleteFileEvent AE_WRITABLE, fd=%d", c->fd);
        aeCreateTimeEvent(loop, delay, WrkCore::delay_request, c, NULL);
    }

    c->request_length = len;
    if(len==0) {
        *c->request = '\0';
        return;
    }

    char **buf = &c->request;
    *buf = (char *) realloc(*buf, len);
    memcpy(*buf, str, len);
}

bool script_is_function(lua_State *L, char *name) {
    lua_getglobal(L, name);
    bool is_function = lua_isfunction(L, -1);
    lua_pop(L, 1);
    return is_function;
}

bool script_is_static(lua_State *L) {
    return !script_is_function(L, "request");
}

bool script_want_response(lua_State *L) {
    return script_is_function(L, "response");
}

bool script_has_delay(lua_State *L) {
    return script_is_function(L, "delay");
}

bool script_has_done(lua_State *L) {
    return script_is_function(L, "done");
}

void script_header_done(lua_State *L, luaL_Buffer *buffer) {
    luaL_pushresult(buffer);
}

void script_summary(lua_State *L, uint64_t duration, uint64_t requests, uint64_t bytes) {
    const table_field fields[] = {
        { "duration", LUA_TNUMBER, &duration },
        { "requests", LUA_TNUMBER, &requests },
        { "bytes",    LUA_TNUMBER, &bytes    },
        { NULL,       0,           NULL      },
    };
    lua_newtable(L);
    set_fields(L, 1, fields);
}

void script_errors(lua_State *L, stats_errors_t *errors) {
    uint64_t e[] = {
        errors->connect,
        errors->read,
        errors->write,
        errors->status,
        errors->timeout
    };
    const table_field fields[] = {
        { "connect", LUA_TNUMBER, &e[0] },
        { "read",    LUA_TNUMBER, &e[1] },
        { "write",   LUA_TNUMBER, &e[2] },
        { "status",  LUA_TNUMBER, &e[3] },
        { "timeout", LUA_TNUMBER, &e[4] },
        { NULL,      0,           NULL  },
    };
    lua_newtable(L);
    set_fields(L, 2, fields);
    lua_setfield(L, 1, "errors");
}

void script_push_stats(lua_State *L, stats_t *s) {
    stats_t **ptr = (stats_t **) lua_newuserdata(L, sizeof(stats_t **));
    *ptr = s;
    luaL_getmetatable(L, "wrk.stats");
    lua_setmetatable(L, -2);
}

static void script_push_error(lua_State *L, stats_errors_t e) {
    lua_newtable(L);
    lua_pushstring(L, "connect");
    lua_pushnumber(L, e.connect);
    lua_settable(L, -3);

    lua_pushstring(L, "read");
    lua_pushnumber(L, e.read);
    lua_settable(L, -3);

    lua_pushstring(L, "write");
    lua_pushnumber(L, e.write);
    lua_settable(L, -3);

    lua_pushstring(L, "status");
    lua_pushnumber(L, e.status);
    lua_settable(L, -3);

    lua_pushstring(L, "timeout");
    lua_pushnumber(L, e.timeout);
    lua_settable(L, -3);
}

static void script_push_node(lua_State *L, uint64_t x, stats_data *p) {
    // {
    //     "path":[{
    //         "x": 1564735298200,
    //         "data":{
    //             "connections:" 2,
    //             "qps": 4456.9930,
    //             "lancency": 100,
    //             "requests": 567,
    //             "error": {
    //                 "connect": 10,
    //                 "read":10,
    //                 "write":10,
    //                 "status":10,
    //                 "timeout":10
    //             }
    //         }
    //     },{

    //     }]
    // }

    lua_newtable(L);
    lua_pushstring(L, "x");
    lua_pushnumber(L, x);
    lua_settable(L,-3);

    // return;
    lua_pushstring(L, "data");
    lua_newtable(L);
    lua_pushstring(L, "connections");
    lua_pushnumber(L, p->connections);
    lua_settable(L, -3);

    lua_pushstring(L, "qps");
    lua_pushnumber(L, p->qps);
    lua_settable(L, -3);

    lua_pushstring(L, "lancency");
    lua_pushnumber(L, p->latency);
    lua_settable(L, -3);

    lua_pushstring(L, "requests");
    lua_pushnumber(L, p->requests);
    lua_settable(L, -3);

    lua_pushstring(L, "error");
    script_push_error(L, p->err);
    lua_settable(L, -3);
    
    lua_settable(L, -3);  
}

static void script_push_series(lua_State *L, series ser) {
    series::iterator x = ser.begin();
    lua_newtable(L);
    for(int key=1; x!=ser.end(); x++, key++) {
        lua_pushinteger(L, key);
        script_push_node(L, x->first, x->second);
        lua_settable(L, -3);
    }
}

static void script_push_stats(lua_State *L, statistics_t *Tstats) {
    stats_t *stats = Tstats->latency;
    lua_newtable(L);
    long double percentiles[] = { 25.0, 50.0, 75.0, 90.0, 99.0 };
    for (size_t i = 0; i < sizeof(percentiles) / sizeof(long double); i++) {
        long double p = percentiles[i];
        uint64_t n = WrkRecord::stats_percentile(stats, p);
        lua_pushstring(L, std::to_string(p).substr(0,4).c_str());
        lua_pushstring(L, format_time_us(n));
        lua_settable(L, -3);
    }
}


static void script_push_connection_t(lua_State *L, wrk_core_connection_t *c) {
    wrk_core_connection_t **p = (wrk_core_connection_t **)lua_newuserdata(L, sizeof(wrk_core_connection_t **));
    *p = c;
    luaL_getmetatable(L, "wrk.connection");
    lua_setmetatable(L, -2);
}


static int script_connection_t_tostring(lua_State *L)
{
    return 0;
}

static int script_connect_t_get(lua_State *L) {
    wrk_core_connection_t *c = (wrk_core_connection_t*)check_connection_t(L);
    if(!c->L) return 0;
    const char *key = lua_tostring(L, -1);
    lua_getglobal(c->L, key);
    script_copy_value(c->L, L, -1);
    lua_pop(c->L, 1);
    return 1;
}

static int script_connect_t_set(lua_State *L) {
    wrk_core_connection_t *c = (wrk_core_connection_t*)check_connection_t(L);
    if(!c->L) return 0;
    const char *name = lua_tostring(L, -2);
    script_copy_value(L, c->L, -1);
    lua_setglobal(c->L, name);
    return 0;
}


static int script_connection_t_index(lua_State *L) {
    wrk_core_connection_t *c = check_connection_t(L);
    const char *method = lua_tostring(L, 2);
    if (!strcmp("method",   method)) lua_pushstring(L, c->request_method);
    if (!strcmp("path",   method)) lua_pushlstring(L, c->request_path.buffer, c->request_path.size);
    if (!strcmp("request",  method)) lua_pushlstring(L, c->request, c->request_length);
    if (!strcmp("response_header", method)) {
        lua_pushstring(L, "response_header");
        lua_newtable(L);
        wrk_core_buffer_t *headers = &c->resp_headers;
        // *c->resp_headers.cursor++ = '\0';
        for (char *ch = headers->buffer; ch < headers->cursor; ) {
            ch = buffer_pushlstring(L, ch);
            ch = buffer_pushlstring(L, ch);
            // tb[k] = v，先后压入table（lua_newtable）, key,vaule,table此时在-3位置
            // 类似lua_settable，但不会触发元方法
            lua_rawset(L, -3);
        }
    }

    if (!strcmp("response_body", method)) lua_pushlstring(L, c->resp_body.buffer, c->resp_body.size);
    if (!strcmp("resquest_header", method)) {
        lua_pushstring(L, "request_header");
        lua_newtable(L);
        *c->request_headers.cursor++ = '\0';
        for (char *ch = c->request_headers.buffer; ch < c->request_headers.cursor; ) {
            ch = buffer_pushlstring(L, ch);
            ch = buffer_pushlstring(L, ch);
            lua_rawset(L, -3);
        }
    }

    if (!strcmp("set", method)) lua_pushcfunction(L, script_connect_t_set);
    if (!strcmp("get", method)) lua_pushcfunction(L, script_connect_t_get);
    
    return 1;
}



void script_done(lua_State *L, map<string, series> map_nodes, map<string, statistics_t *> map_stats) {
    lua_getglobal(L, "done");

    cout.flags(ios::fixed);
    cout.precision(4);
    lua_newtable(L);

    map<string, series>::iterator it = map_nodes.begin();
    for(; it!=map_nodes.end(); it++) {
        lua_pushstring(L, it->first.c_str());
        script_push_series(L, it->second);
        lua_settable(L, -3);
    }

    lua_newtable(L);
    map<string, statistics_t *>::iterator iter = map_stats.begin();
    for(; iter!=map_stats.end(); iter++) {
        string path = iter->first;
        lua_pushstring(L, path.c_str());
        script_push_stats(L, iter->second);
        lua_settable(L, -3);
    }

    if(lua_pcall(L, 2, 0, 0)) {
        throw string("done function error: ") + string(lua_tostring(L, -1));
    }
    
    lua_pop(L, 2);
}

static int verify_request(http_parser *parser) {
    size_t *count = (size_t *)parser->data;
    (*count)++;
    return 0;
}

size_t script_verify_request(lua_State *L, wrk_core_connection_t *connect) {
    http_parser_settings settings = {
        on_message_begin: NULL,
        on_url  : NULL,
        on_status: NULL,
        on_header_field: NULL,
        on_header_value: NULL,
        on_headers_complete: NULL,
        on_body : NULL,
        on_message_complete : verify_request,
        on_chunk_header : NULL,
        on_chunk_complete : NULL
    };
    http_parser parser;

    size_t count = 0;

    // script_request(L, connect);
    http_parser_init(&parser, HTTP_REQUEST);

    // parser.data与count是一个东西，别名，同一块内存,每解析完一个request会触发一次verify_request
    parser.data = &count;

    //会修改parser，parser.data也会被修改，即count值被设置了
    size_t parsed = http_parser_execute(&parser, &settings, connect->request, connect->request_length);

    if (parsed != connect->request_length || count == 0) {
        enum http_errno err = HTTP_PARSER_ERRNO(&parser);
        const char *desc = http_errno_description(err);
        const char *msg = err != HPE_OK ? desc : "incomplete request";
        int line = 1, column = 1;

        for (char *c = connect->request; c < connect->request + parsed; c++) {
            column++;
            if (*c == '\n') {
                column = 1;
                line++;
            }
        }

        fprintf(stderr, "%s at %d:%d\n", msg, line, column);
        stringstream ss;
        ss << msg << " at "<<line << ":"<< column;
        throw ss.str();
    }

    connect->pipeline = count;
    connect->pending  = count;

    return count;
}

static struct addrinfo *checkaddr(lua_State *L) {
    struct addrinfo *addr = (struct addrinfo *) luaL_checkudata(L, -1, "wrk.addr");
    luaL_argcheck(L, addr != NULL, 1, "`addr' expected");
    return addr;
}

void script_addr_copy(struct addrinfo *src, struct addrinfo *dst) {
    *dst = *src;
    dst->ai_addr = (struct sockaddr *)zmalloc(src->ai_addrlen);
    memcpy(dst->ai_addr, src->ai_addr, src->ai_addrlen);
}

struct addrinfo *script_addr_clone(lua_State *L, struct addrinfo *addr) {
    struct addrinfo *udata = (struct addrinfo *)lua_newuserdata(L, sizeof(*udata));
    luaL_getmetatable(L, "wrk.addr");
    lua_setmetatable(L, -2);
    script_addr_copy(addr, udata);
    return udata;
}

static int script_addr_tostring(lua_State *L) {
    struct addrinfo *addr = (struct addrinfo*)checkaddr(L);
    char host[NI_MAXHOST];
    char service[NI_MAXSERV];

    int flags = NI_NUMERICHOST | NI_NUMERICSERV;
    int rc = getnameinfo(addr->ai_addr, addr->ai_addrlen, host, NI_MAXHOST, service, NI_MAXSERV, flags);
    if (rc != 0) {
        const char *msg = gai_strerror(rc);
        return luaL_error(L, "addr tostring failed %s", msg);
    }

    lua_pushfstring(L, "%s:%s", host, service);
    return 1;
}

static int script_addr_gc(lua_State *L) {
    struct addrinfo *addr = checkaddr(L);
    zfree(addr->ai_addr);
    return 0;
}

static stats_t *checkstats(lua_State *L) {
    stats_t **s = (stats_t**)luaL_checkudata(L, 1, "wrk.stats");
    luaL_argcheck(L, s != NULL, 1, "`stats' expected");
    return *s;
}

static wrk_core_connection_t *check_connection_t(lua_State *L) {
    wrk_core_connection_t **s = (wrk_core_connection_t**)luaL_checkudata(L, 1, "wrk.connection");
    luaL_argcheck(L, s != NULL, 1, "`connection' expected");
    return *s;
}

static int script_stats_percentile(lua_State *L) {
    stats_t *s = (stats_t*)checkstats(L);
    lua_Number p = luaL_checknumber(L, 2);
    lua_pushnumber(L, WrkRecord::stats_percentile(s, p));
    return 1;
}

static int script_stats_call(lua_State *L) {
    stats_t *s = (stats_t*)checkstats(L);
    uint64_t index = lua_tonumber(L, 2);
    uint64_t count;
    lua_pushnumber(L, WrkRecord::stats_value_at(s, index - 1, &count));
    lua_pushnumber(L, count);
    return 2;
}

static int script_stats_index(lua_State *L) {
    stats_t *s = checkstats(L);
    const char *method = lua_tostring(L, 2);
    if (!strcmp("min",   method)) lua_pushnumber(L, s->min);
    if (!strcmp("max",   method)) lua_pushnumber(L, s->max);
    if (!strcmp("mean",  method)) lua_pushnumber(L, WrkRecord::stats_mean(s));
    if (!strcmp("stdev", method)) lua_pushnumber(L, WrkRecord::stats_stdev(s, WrkRecord::stats_mean(s)));
    if (!strcmp("percentile", method)) {
        lua_pushcfunction(L, script_stats_percentile);
    }
    return 1;
}

static int script_stats_len(lua_State *L) {
    stats_t *s = checkstats(L);
    lua_pushinteger(L, WrkRecord::stats_popcount(s));
    return 1;
}




static wrk_core_thread_t *checkthread(lua_State *L) {
    wrk_core_thread_t **t = (wrk_core_thread_t**)luaL_checkudata(L, 1, "wrk.thread");
    luaL_argcheck(L, t != NULL, 1, "`thread' expected");
    return *t;
}

static int script_thread_get(lua_State *L) {
    wrk_core_thread_t *t = (wrk_core_thread_t*)checkthread(L);
    const char *key = lua_tostring(L, -1);
    lua_getglobal(t->L, key);
    script_copy_value(t->L, L, -1);
    lua_pop(t->L, 1);
    return 1;
}

static int script_thread_set(lua_State *L) {
    wrk_core_thread_t *t = checkthread(L);
    const char *name = lua_tostring(L, -2);
    script_copy_value(L, t->L, -1);
    lua_setglobal(t->L, name);
    return 0;
}

static int script_thread_stop(lua_State *L) {
    wrk_core_thread_t *t = checkthread(L);
    aeStop(t->loop);
    return 0;
}

static int script_thread_index(lua_State *L) {
    wrk_core_thread_t *t = checkthread(L);
    const char *key = lua_tostring(L, 2);
    if (!strcmp("get",  key)) lua_pushcfunction(L, script_thread_get);
    if (!strcmp("set",  key)) lua_pushcfunction(L, script_thread_set);
    if (!strcmp("stop", key)) lua_pushcfunction(L, script_thread_stop);
    if (!strcmp("addr", key)) script_addr_clone(L, t->addr);
    return 1;
}

static int script_thread_newindex(lua_State *L) {
    wrk_core_thread_t *t = checkthread(L);
    const char *key = lua_tostring(L, -2);
    if (!strcmp("addr", key)) {
        struct addrinfo *addr = checkaddr(L);
        if (t->addr){
            zfree(t->addr->ai_addr);
        } 

        t->addr = (struct addrinfo *)zrealloc(t->addr, sizeof(*addr));
        script_addr_copy(addr, t->addr);
    } else {
        luaL_error(L, "cannot set '%s' on thread", luaL_typename(L, -1));
    }
    return 0;
}

static int script_wrk_lookup(lua_State *L) {
    struct addrinfo *addrs;

    // int	ai_flags;	/* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
	// int	ai_family;	/* PF_xxx */
	// int	ai_socktype;	/* SOCK_xxx */
	// int	ai_protocol;	/* 0 or IPPROTO_xxx for IPv4 and IPv6 */
	// socklen_t ai_addrlen;	/* length of ai_addr */
	// char	*ai_canonname;	/* canonical name for hostname */
	// struct	sockaddr *ai_addr;	/* binary address */
	// struct	addrinfo *ai_next;	/* next structure in linked list */
    struct addrinfo hints = {
        .ai_flags = 0,
        .ai_family   = AF_UNSPEC,
        .ai_socktype = SOCK_STREAM
    };
    int rc, index = 1;

    const char *host    = lua_tostring(L, -2);
    const char *service = lua_tostring(L, -1);

    if ((rc = getaddrinfo(host, service, &hints, &addrs)) != 0) {
        const char *msg = gai_strerror(rc);
        fprintf(stderr, "unable to resolve %s:%s %s\n", host, service, msg);
        exit(1);
    }

    lua_newtable(L);
    for (struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next) {
        script_addr_clone(L, addr);
        lua_rawseti(L, -2, index++);
    }

    freeaddrinfo(addrs);
    return 1;
}

static int script_wrk_connect(lua_State *L) {
    struct addrinfo *addr = checkaddr(L);
    int fd, connected = 0;
    if ((fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) != -1) {
        connected = connect(fd, addr->ai_addr, addr->ai_addrlen) == 0;
        close(fd);
    }
    lua_pushboolean(L, connected);
    return 1;
}

void script_copy_value(lua_State *src, lua_State *dst, int index) {
    switch (lua_type(src, index)) {
        case LUA_TBOOLEAN:
            lua_pushboolean(dst, lua_toboolean(src, index));
            break;
        case LUA_TNIL:
            lua_pushnil(dst);
            break;
        case LUA_TNUMBER:
            lua_pushnumber(dst, lua_tonumber(src, index));
            break;
        case LUA_TSTRING:
            lua_pushstring(dst, lua_tostring(src, index));
            break;
        case LUA_TTABLE:
            lua_newtable(dst);
            lua_pushnil(src);
            while (lua_next(src, index - 1)) {
                script_copy_value(src, dst, -2);
                script_copy_value(src, dst, -1);
                lua_settable(dst, -3);
                lua_pop(src, 1);
            }
            lua_pop(src, 1);
            break;
        default:
            luaL_error(src, "cannot transfer '%s' to thread", luaL_typename(src, index));
    }
}

int script_parse_url(char *url, struct http_parser_url *parts) {
    if (!http_parser_parse_url(url, strlen(url), 0, parts)) {
        if (!(parts->field_set & (1 << UF_SCHEMA))) return 0;
        if (!(parts->field_set & (1 << UF_HOST)))   return 0;
        return 1;
    }
    return 0;
}

static int push_url_part(lua_State *L, char *url, struct http_parser_url *parts, enum http_parser_url_fields field) {
    int type = parts->field_set & (1 << field) ? LUA_TSTRING : LUA_TNIL;
    uint16_t off, len;
    switch (type) {
        case LUA_TSTRING:
            off = parts->field_data[field].off;
            len = parts->field_data[field].len;
            lua_pushlstring(L, &url[off], len);
            break;
        case LUA_TNIL:
            lua_pushnil(L);
    }
    return type;
}


// void lua_setfield (lua_State *L, int index, const char *k);
// Does the equivalent to t[k] = v, where t is the value at the given valid index and v is the value at the top of the stack.
static void set_field(lua_State *L, int index, char *field, int type) {
    (void) type;
    lua_setfield(L, index, field);
}

static void set_fields(lua_State *L, int index, const table_field *fields) {
    for (int i = 0; fields[i].name; i++) {
        table_field f = fields[i];
        switch (f.value == NULL ? LUA_TNIL : f.type) {
            case LUA_TFUNCTION:
                lua_pushcfunction(L, (lua_CFunction) f.value);
                break;
            case LUA_TNUMBER:
                lua_pushinteger(L, *((lua_Integer *) f.value));
                break;
            case LUA_TSTRING:
                lua_pushstring(L, (const char *) f.value);
                break;
            case LUA_TNIL:
                lua_pushnil(L);
                break;
        }
        lua_setfield(L, index, f.name);
    }
}

void buffer_append(wrk_core_buffer_t *b, const char *data, size_t len) {
    size_t used = b->cursor - b->buffer;
    while (used + len + 1 >= b->length) {
        b->length += 1024;
        b->buffer  = (char *)realloc(b->buffer, b->length);
        b->cursor  = b->buffer + used;
    }
    memcpy(b->cursor, data, len);
    b->cursor += len;
    b->size = b->cursor - b->buffer;
}

void buffer_reset(wrk_core_buffer_t *b) {
    b->cursor = b->buffer;
    b->size   = 0;
}

char *buffer_pushlstring(lua_State *L, char *start) {
    //查找0的位置，即字符器分隔符
    //分隔出key value，返回下个段的地址
    char *end = strchr(start, 0);
    lua_pushlstring(L, start, end - start);
    return end + 1;
}
